package com.atguigu.paymentdemo.service.impl;

import com.atguigu.paymentdemo.config.WxPayConfig;
import com.atguigu.paymentdemo.entity.OrderInfo;
import com.atguigu.paymentdemo.entity.RefundInfo;
import com.atguigu.paymentdemo.enums.OrderStatus;
import com.atguigu.paymentdemo.enums.PayType;
import com.atguigu.paymentdemo.enums.wxpay.WxApiType;
import com.atguigu.paymentdemo.enums.wxpay.WxNotifyType;
import com.atguigu.paymentdemo.enums.wxpay.WxRefundStatus;
import com.atguigu.paymentdemo.enums.wxpay.WxTradeState;
import com.atguigu.paymentdemo.service.OrderInfoService;
import com.atguigu.paymentdemo.service.PaymentInfoService;
import com.atguigu.paymentdemo.service.RefundInfoService;
import com.atguigu.paymentdemo.service.WxPayService;
import com.atguigu.paymentdemo.util.OrderNoUtils;
import com.atguigu.paymentdemo.vo.R;
import com.google.gson.Gson;
import com.mysql.cj.util.StringUtils;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author longge
 * @Title:
 * @Description:
 * @date 2023/2/5 13:57
 */
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private WxPayConfig wxPayConfig;

    @Autowired
    private CloseableHttpClient wxPayClient;

    @Autowired
    private OrderInfoService orderInfoService;

    @Autowired
    private PaymentInfoService paymentInfoService;

    @Autowired
    private RefundInfoService refundInfoService;

    @Autowired
    private CloseableHttpClient wxPayNoSignClient;

    private final ReentrantLock lock = new ReentrantLock();


    /**
     * @Description: navtive下单，创建订单
     * @param productId 商品id
     * @return codeUrl：二维码  orderNo：订单号
     * @throws IOException
     */
    @Override
    public Map<String, Object> nativePay(Long productId) throws IOException {
        log.info("生成订单");
        //生成订单
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.WXPAY.getType());
        if(orderInfo!=null && !StringUtils.isNullOrEmpty(orderInfo.getCodeUrl())){
            log.info("二维码订单已存在");
            HashMap<String, Object> map = new HashMap<>();
            map.put("codeUrl",orderInfo.getCodeUrl());
            map.put("orderNo",orderInfo.getOrderNo());
            return map;
        }

        log.info("调用统一下单API");
        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        // 请求body参数
        Gson gson = new Gson();
        HashMap<Object, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid",wxPayConfig.getAppid());
        paramsMap.put("mchid",wxPayConfig.getMchId());
        paramsMap.put("description",orderInfo.getTitle());
        paramsMap.put("out_trade_no",orderInfo.getOrderNo());
        paramsMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));

        HashMap<Object, Object> amountMap = new HashMap<>();
        amountMap.put("total",orderInfo.getTotalFee());
        amountMap.put("currency","CNY");
        paramsMap.put("amount",amountMap);

        //转化json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数：{}",jsonParams);

        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                //处理成功
                log.info("native成功，返回结果：{} ", bodyAsString);
            } else if (statusCode == 204) {
                //处理成功，无返回Body
                log.info("native成功，无返回结果");
            } else {
                log.info("native下单失败===> statusCode:{},body:{}", statusCode, bodyAsString);
                throw new IOException("request failed");
            }
            HashMap resultMap = gson.fromJson(bodyAsString, HashMap.class);
            String codeUrl = (String) resultMap.get("code_url");

            //保存二维码
            orderInfoService.saveCodeUrl(orderInfo.getOrderNo(),codeUrl);

            //返回结果
            HashMap<String, Object> map = new HashMap<>();
            map.put("codeUrl",codeUrl);
            map.put("orderNo",orderInfo.getOrderNo());
            return map;
        } finally {
            response.close();
        }
    }

    /**
     * @Description: 处理订单
     * @param bodyMap 微信回调body信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processOrder(HashMap<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("处理订单");
        //将密文解密成明文
        String plainText = decryptFromSource(bodyMap);
        Gson gson = new Gson();
        HashMap plainMap = gson.fromJson(plainText, HashMap.class);

        if(lock.tryLock()) {
            try {
                //更新订单状态
                Object outTradeNo = plainMap.get("out_trade_no");

                //接口调用的幂等性
                String status = orderInfoService.getOrderStatus(outTradeNo);
                if (!OrderStatus.NOTPAY.getType().equals(status)) {
                    return;
                }

                //更新订单状态
                orderInfoService.updateStausByOrderNo(outTradeNo, OrderStatus.SUCCESS);

                //记录支付日志
                paymentInfoService.createPaymentInfo(plainText);
                log.info("处理订单成功==>{}",outTradeNo);

            } finally {
                lock.unlock();
            }
        }else {
            throw new RuntimeException("获取锁失败");
        }

    }

    /**
     * @Description: 取消订单
     * @param orderNo 订单号
     */
    @Override
    public void cancelOrder(String orderNo) throws IOException {
        //调用微信关单接口
        this.closeOrder(orderNo);

        //更新订单状态
        orderInfoService.updateStausByOrderNo(orderNo,OrderStatus.CANCEL);
    }


    /**
     * @Description: 查询订单状态
     * @param orderNo   订单号
     * @return
     */
    @Override
    public String queryOrder(String orderNo) throws IOException {
        log.info("查询订单接口调用 ===》 {}",orderNo);

        String url = String.format(wxPayConfig.getDomain().concat(WxApiType.ORDER_QUERY_BY_NO.getType()), orderNo);
        url = url.concat("?mchid="+wxPayConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                //处理成功
                log.info("查单成功，返回结果：{} ", bodyAsString);
            } else if (statusCode == 204) {
                //处理成功，无返回Body
                log.info("查单成功，无返回结果");
            } else {
                log.info("查单成功失败===> statusCode:{},body:{}", statusCode, bodyAsString);
                throw new IOException("request failed");
            }
            return bodyAsString;
        } finally {
            response.close();
        }
    }


    /**
     * @Description: 调微信接口查询订单，
     *                  如果订单已支付，更新订单
     *                  如果订单未支付，关闭订单
     * @param orderNo   订单号
     */
    @Override
    public void checkOrderStatus(String orderNo) throws IOException {
        log.info("核实订单状态 ===》 {}",orderNo);
        String result = queryOrder(orderNo);
        Gson gson = new Gson();
        HashMap hashMap = gson.fromJson(result, HashMap.class);

        //获取订单状态
        String tradeState = (String) hashMap.get("trade_state");
        if (WxTradeState.SUCCESS.getType().equals(tradeState)){
            log.info("核实订单已支付 ===> {}",orderNo);

            //更新本地订单状态
            orderInfoService.updateStausByOrderNo(orderNo,OrderStatus.SUCCESS);
            //记录支付日志
            paymentInfoService.createPaymentInfo(result);
        }

        if(WxTradeState.NOTPAY.getType().equals(tradeState)){
            //关闭订单
            cancelOrder(orderNo);
            //更新本地订单状态
            orderInfoService.updateStausByOrderNo(orderNo,OrderStatus.CLOSED);
        }
    }


    /**
     * @Description: 申请退款
     * @param orderNo   订单号
     * @param reason    退款原因
     */
    @Override
    public void refund(String orderNo, String reason) throws IOException {
        log.info("创建退款单记录");
        RefundInfo refundInfo = refundInfoService.createRefundByOrderNo(orderNo, reason);

        log.info("调用申请退款API");
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);

        Gson gson = new Gson();
        HashMap<Object, Object> paramsMap = new HashMap<>();
        paramsMap.put("out_trade_no",refundInfo.getOrderNo());
        paramsMap.put("out_refund_no",refundInfo.getRefundNo());
        paramsMap.put("reason",reason);
        paramsMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));

        HashMap<Object, Object> amount = new HashMap<>();
        amount.put("refund",refundInfo.getRefund());
        amount.put("total",refundInfo.getTotalFee());
        amount.put("currency","CNY");
        paramsMap.put("amount",amount);

        String jsonBody = gson.toJson(paramsMap);
        log.info("申请退款的请求参数 ===》 {}",jsonBody);

        StringEntity entity = new StringEntity(jsonBody,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                //处理成功
                log.info("申请退款成功，返回结果：{} ", bodyAsString);
            } else if (statusCode == 204) {
                //处理成功，无返回Body
                log.info("申请退款成功，无返回结果");
            } else {
                log.info("申请退款失败===> statusCode:{},body:{}", statusCode, bodyAsString);
                throw new IOException("request failed");
            }

            //更新订单状态
            orderInfoService.updateStausByOrderNo(orderNo,OrderStatus.REFUND_PROCESSING);

            //更新退款单
            refundInfoService.updateRefund(bodyAsString);

        } finally {
            response.close();
        }
    }

    /**
     * @Description: 查询退款
     * @param refundNo   退款单号
     * @return  wx返回报文
     */
    @Override
    public String queryRefund(String refundNo) throws IOException {
        log.info("退款接口调用 ===》 {}",refundNo);
        String url = String.format(wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS_QUERY.getType()), refundNo);
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept","application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                //处理成功
                log.info("查询退款成功，返回结果：{} ", bodyAsString);
            } else if (statusCode == 204) {
                //处理成功，无返回Body
                log.info("查询退款成功，无返回结果");
            } else {
                log.info("查询退款失败===> statusCode:{},body:{}", statusCode, bodyAsString);
                throw new IOException("request failed");
            }
            return bodyAsString;
        } finally {
            response.close();
        }
    }

    /**
     * @Description: 核查退款单
     * @param refundNo  退款号
     */
    @Override
    public void checkRefundOrder(String refundNo) throws IOException {
        //退款查询
        String bodyAsString = queryRefund(refundNo);
        //更新退款单
        refundInfoService.updateRefund(bodyAsString);

        Gson gson = new Gson();
        HashMap hashMap = gson.fromJson(bodyAsString, HashMap.class);
        String outTradeNo = (String) hashMap.get("out_trade_no");
        String status = (String) hashMap.get("status");
        if(WxRefundStatus.SUCCESS.getType().equals(status)){
            orderInfoService.updateStausByOrderNo(outTradeNo,OrderStatus.REFUND_SUCCESS);
        }else if(WxRefundStatus.PROCESSING.getType().equals(status)){
            orderInfoService.updateStausByOrderNo(outTradeNo,OrderStatus.REFUND_PROCESSING);
        }else {
            orderInfoService.updateStausByOrderNo(outTradeNo,OrderStatus.REFUND_ABNORMAL);
        }

    }

    /**
     * @Description: 处理退款单
     * @param bodyMap   微信通知消息体
     */
    @Override
    public void processRefund(HashMap<String,Object> bodyMap) throws GeneralSecurityException {
        log.info("处理退款单");
        String plainText = decryptFromSource(bodyMap);
        Gson gson = new Gson();
        HashMap plainMap = gson.fromJson(plainText, HashMap.class);

        if(lock.tryLock()){
            try {
                //更新订单状态
                Object outTradeNo = plainMap.get("out_trade_no");

                //接口调用的幂等性
                String status = orderInfoService.getOrderStatus(outTradeNo);
                if(!OrderStatus.REFUND_PROCESSING.getType().equals(status)){
                    return;
                }

                //更新订单状态
                orderInfoService.updateStausByOrderNo(outTradeNo, OrderStatus.REFUND_SUCCESS);

                //更新退款单状态
                refundInfoService.updateRefund(plainText);
                log.info("处理退款单成功==>{}",outTradeNo);
            }catch (Exception e){
                throw new RuntimeException("退款通知异常",e);
            }finally {
                lock.unlock();
            }
        }else {
            throw new RuntimeException("获取锁失败");
        }
    }

    /**
     * @Description: 获取账单url
     * @param billDate  日期
     * @param type  账单类型
     * @return
     */
    @Override
    public String queryBill(String billDate, String type) throws IOException {
        log.info("查询账单接口调用 ==》 {} - {}",billDate,type);
        String url = "";
        if("tradebill".equals(type)){
            url = WxApiType.TRADE_BILLS.getType();
        }else if("fundflowbill".equals(type)){
            url = WxApiType.FUND_FLOW_BILLS.getType();
        }else {
            throw new RuntimeException("不支持此账单类型");
        }

        url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);

        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                //处理成功
                log.info("查询账单成功，返回结果：{} ", bodyAsString);
            } else if (statusCode == 204) {
                //处理成功，无返回Body
                log.info("查询账单成功，无返回结果");
            } else {
                log.info("查询账单失败===> statusCode:{},body:{}", statusCode, bodyAsString);
                throw new RuntimeException("request failed");
            }

            Gson gson = new Gson();
            HashMap hashMap = gson.fromJson(bodyAsString, HashMap.class);
            return (String) hashMap.get("download_url");
        } finally {
            response.close();
        }
    }


    /**
     * @Description: 下载账单
     * @param billDate  日期
     * @param type  账单类型
     * @return
     */
    @Override
    public String downloadBill(String billDate, String type) throws IOException {
        log.info("请求微信下载账单接口 ==> {}-{}",billDate,type);
        String url = queryBill(billDate, type);
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                //处理成功
                log.info("查询账单成功，返回结果：{} ", bodyAsString);
            } else if (statusCode == 204) {
                //处理成功，无返回Body
                log.info("查询账单成功，无返回结果");
            } else {
                log.info("查询账单失败===> statusCode:{},body:{}", statusCode, bodyAsString);
                throw new RuntimeException("request failed");
            }

            return bodyAsString;
        } finally {
            response.close();
        }
    }

    /**
     * @Description: 关闭微信订单
     * @param orderNo 订单号
     */
    private void closeOrder(String orderNo) throws IOException {
        log.info("关单接口调用，订单号===> {}",orderNo);

        String url = wxPayConfig.getDomain().concat(WxApiType.CLOSE_ORDER_BY_NO.getType());
        url= String.format(url, orderNo);
        HttpPost httpPost = new HttpPost(url);

        //构建请求体
        Gson gson = new Gson();
        HashMap<String, String> paramsMap = new HashMap<>();
        paramsMap.put("mchid",wxPayConfig.getMchId());
        String bodyJson = gson.toJson(paramsMap);
        log.info("请求参数===>{}",bodyJson);

        StringEntity entity = new StringEntity(bodyJson,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try {
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                //处理成功
                log.info("关单成功200");
            } else if (statusCode == 204) {
                //处理成功，无返回Body
                log.info("关单成功204");
            } else {
                log.info("关单失败");
                throw new IOException("request failed");
            }

        } finally {
            response.close();
        }

    }


    /**
     * @Description: 解密
     * @param bodyMap   加密报文
     * @return  解密字符串
     * @throws GeneralSecurityException
     */
    private String decryptFromSource(HashMap<String, Object> bodyMap) throws GeneralSecurityException {
        Map<String ,String> resourceMap = (Map<String, String>) bodyMap.get("resource");
        String ciphertext = resourceMap.get("ciphertext");
        String nonce = resourceMap.get("nonce");
        String associatedData = resourceMap.get("associated_data");

        log.info("密文：{}",ciphertext);
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String decryptToString = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        log.info("明文：{}",decryptToString);

        return decryptToString;
    }
}
